from scipy import average

class AgentInfo(object):
    """
    Data structure that makes information available to an agent.
    All methods accessible to agents are defined as read-only properties.
    """
    def __init__(self,agent_id,game):
        """Constructor, not intended to be called by agents. """
        self._agent_id = agent_id
        self._game = game
        self._world = self._game.world
        self._H = self._game.H
        self._num_agents = len(self._game.A)

    def _get_agent_id(self,agent_id):
        """Private method to convert agent_id parameters."""
        assert(agent_id == None or 
               (agent_id >= 0 and agent_id < self._num_agents))
        return agent_id if agent_id else self._agent_id

    def _get_round(self,round_num):
        """Private method to convert round_num parameters to list indices."""
        
        assert((round_num == None and self._game.round > 1) or
               (round_num > 1 and round_num < self._game.round))
        return round_num if round_num else self._game.round - 2
 
    def _get_agent_id_and_round(self,agent_id,round_num):
        """Private methods to convert agent_id and round_num parameters."""
        return self._get_agent_id(agent_id), self._get_round(round_num)
    
    # The following methods are all public accessor methods
    @property
    def current_round(self):
        """Return integer corresponding to the current round."""
        return self._game.round
    
    @property
    def total_rounds(self):
        """Return the total number of rounds in this game."""
        return self._game.num_rounds
    
    @property
    def cost_of_investing(self):
        """Return the cost of investing in each round."""
        return self._world.cost_of_investing

    @property
    def cost_of_negevent(self):
        """Return the cost of a negative event."""
        return self._world.cost_of_negevent

    @property
    def num_agents(self):
        """Return the total number of agents in this game."""
        return self._num_agents
        
    def invested(self, agent_id=None, round_num=None):
        """Report whether an agent has invested at a given round."""
        agent_id,round_num = self._get_agent_id_and_round(agent_id,round_num)
        return self._H[round_num][agent_id]['invested']

    def outcome(self, agent_id=None, round_num=None):
        """Report the outcome received by an agent at a given round."""
        agent_id,round_num = self._get_agent_id_and_round(agent_id,round_num)
        return self._H[round_num][agent_id]['outcome']

    def negevent(self, agent_id=None, round_num=None):
        """Report True if an agent experienced a negative event at a given 
            round."""
        agent_id,round_num = self._get_agent_id_and_round(agent_id,round_num)
        return self._H[round_num][agent_id]['negevent']
        
    def start_balance(self, agent_id=None, round_num=None):
        """Report the starting balance for an agent at a given round."""
        agent_id,round_num = self._get_agent_id_and_round(agent_id,round_num)
        return self._H[round_num][agent_id]['start_balance']

    def end_balance(self, agent_id=None, round_num=None):
        """Report the ending balance for an agent at a given round."""
        agent_id,round_num = self._get_agent_id_and_round(agent_id,round_num)
        return self._H[round_num][agent_id]['end_balance']
    
    def current_balance(self, agent_id=None):
        """Report an agent's current balance."""
        agent_id = self._get_agent_id(agent_id)
        return self._H[-1][agent_id]['start_balance']

    def all_decisions(self, round_num=None):
        """Report a list of invest decisions by all agents in a given round, 
        ordered by agent id."""
        round_num = self._get_round(round_num)
        return [x['invested'] for x in self._H[round_num]]

    def neighbors(self, agent_id=None):
        """Report list of current neighbors for an agent."""
        agent_id = self._get_agent_id(agent_id)
        return self._world.neighbors(agent_id)

    def neighborhood_size(self, agent_id=None):
        """Report current neighborhood size for an agent (including the agent)."""
        agent_id = self._get_agent_id(agent_id)
        return len(self._world.neighbors(agent_id)) + 1

    def ave_agent_outcome(self, agent_id=None):
        """Report the average of the outcomes received by an agent so far."""
        agent_id = self._get_agent_id(agent_id)
        return average([r[agent_id]['outcome'] for r in self._H])

    def ave_round_outcome(self, round_num=None):
        """Report the average outcome by all agents in a given round."""
        round_num = self._get_round(round_num)
        return average([x['outcome'] for x in self._H[round_num]])

    def prob_agent_investment(self, agent_id=None):
        """Report the probability that an agent has invested in all previous 
        rounds."""
        agent_id = self._get_agent_id(agent_id)
        return average([r[agent_id]['invested'] for r in self._H])

    def prob_round_investment(self, round_num=None):
        """Report the probability that agents in a given round have invested."""
        round_num = self._get_round(round_num)
        return average([x['invested'] for x in self._H[round_num]])

    def p_negevent(self, agent_id=None, decisions=None):
        """Report the probability of a negative event for agent_id, given a list
        of decisions by all agents."""
        agent_id = self._get_agent_id(agent_id)
        decisions = decisions if decisions else self.all_decisions()
        return self._world.p_negevent(agent_id,decisions)